package com.wh1200.mybatis.xmlless.methods

import com.wh1200.kotlin.isNotNullAndNotBlank
import com.wh1200.mybatis.xmlless.AnnotationSupport
import com.wh1200.mybatis.xmlless.config.MybatisXmlLessConfiguration
import com.wh1200.mybatis.xmlless.enums.MethodType
import com.wh1200.mybatis.xmlless.generator.IdGeneratorUtil
import com.wh1200.mybatis.xmlless.model.MethodInfo
import com.wh1200.mybatis.xmlless.model.ResolvedQueries
import com.wh1200.mybatis.xmlless.model.ResolvedQuery
import com.wh1200.mybatis.xmlless.model.dto.DefaultMethodWrapper
import com.wh1200.mybatis.xmlless.resolver.MappingResolver
import com.wh1200.mybatis.xmlless.resolver.QueryResolver
import com.wh1200.mybatis.xmlless.util.AnnotationUtil
import com.wh1200.mybatis.xmlless.util.XmllessInfoUtil
import com.wh1200.mybatis.xmlless.util.getXmllessTableInfo
import com.baomidou.mybatisplus.annotation.IdType
import com.baomidou.mybatisplus.core.injector.AbstractMethod
import com.baomidou.mybatisplus.core.metadata.TableInfo
import com.wh1200.kotlin.startsWithAny
import com.wh1200.mybatis.xmlless.XmlLessMapper
import com.wh1200.mybatis.xmlless.config.MybatisXmlLessMapperAnnotationBuilder
import org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator
import org.apache.ibatis.executor.keygen.NoKeyGenerator
import org.apache.ibatis.mapping.MappedStatement
import org.apache.ibatis.mapping.SqlCommandType
import org.apache.ibatis.mapping.SqlSource
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.lang.reflect.Field


/**
 *
 * Created by 吴昊 on 2018-12-09.
 *
 * @author 吴昊
 * @since 0.0.1
 */
class XmlLessMethods : AbstractMethod("") {

  companion object {
    const val COUNT_STATEMENT_SUFFIX = "CountAllSuffix"
    private val LOG: Logger = LoggerFactory.getLogger(XmlLessMethods::class.java)
    private val possibleErrors = listOf(
        "未在级联属性@ModifyIgnore注解将其标记为不需要插入或更新的字段",
        "复杂对象未指定TypeHandler"
    )
  }

  override fun injectMappedStatement(
      mapperClass: Class<*>,
      modelClass: Class<*>,
      tableInfo: TableInfo
  ): MappedStatement? {
    if (!XmlLessMapper::class.java.isAssignableFrom(mapperClass)) {
      return null
    }
    // 判断Mapper方法是否已经定义了sql声明，如果没有定义才进行注入，这样如果存在Mapper方法在xml文件中有定义则会优先使用，如果没有定义才会进行推断
    val unmappedMethods = mapperClass.methods.filter {
      it.declaringClass != Object::class.java && MybatisXmlLessMapperAnnotationBuilder.canHaveStatement(it)
    }.filter {
      !configuration.hasStatement("${mapperClass.name}$DOT${it.name}")
    }.filter {
      // 过滤mybatis-plus的生成方法
      !it.declaringClass.name.startsWithAny("com.baomidou.mybatisplus.core.mapper", "com.github.yulichang.base")
    }
    // 解析未定义的方法，进行sql推断
    val resolvedQueries = ResolvedQueries(mapperClass)
    val xmllessTableInfo = getXmllessTableInfo(tableInfo.entityType)
    unmappedMethods.forEach { method ->
      val methodInfo = QueryResolver.resolveMethodInfo(
          DefaultMethodWrapper(method, mapperClass, modelClass, xmllessTableInfo),
          xmllessTableInfo,
          (builderAssistant.configuration as MybatisXmlLessConfiguration).dbType
      )
      try {
        val resolvedQuery = QueryResolver.resolveQuery(methodInfo)
        XmllessInfoUtil.addResolvedQuery(resolvedQuery)
        resolvedQueries.add(resolvedQuery)
        // query为null则表明推断失败，resolvedQuery中将包含推断失败的原因，会在后面进行统一输出，方便开发人员了解sql推断的具体结果和失败的具体原因
        if (resolvedQuery.query != null && resolvedQuery.sql != null) {
          resolve(resolvedQuery, mapperClass, tableInfo, methodInfo, modelClass)
        }
      } catch (e: Exception) {
        LOG.error("解析方法 ${methodInfo.fullMethodName} 失败", e)
      }
    }
    resolvedQueries.log()
    // 其实这里的return是没有必要的，mybatis plus也没有对这个返回值做任何的处理，
    // 所里这里随便返回了一个sql声明
    return addSelectMappedStatementForTable(
        mapperClass,
        "unknown",
        languageDriver.createSqlSource(configuration, "select 1", modelClass),
        tableInfo
    )
  }

  private fun resolve(
      resolvedQuery: ResolvedQuery,
      mapperClass: Class<*>,
      tableInfo: TableInfo,
      methodInfo: MethodInfo,
      modelClass: Class<*>
  ) {
    val sql = resolvedQuery.sql
    try {
      val sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass)
      when (resolvedQuery.type!!) {
        MethodType.Select,
        MethodType.SelectDistinct,
        MethodType.Count       -> {
          resolveCount(resolvedQuery, methodInfo, modelClass, mapperClass, sqlSource, tableInfo)
        }

        MethodType.Clean,
        MethodType.Delete      -> {
          addDeleteMappedStatement(mapperClass, methodInfo.name, sqlSource)
        }

        MethodType.InsertOrUpdate,
        MethodType.Insert      -> {
          // 如果id类型为自增，则将自增的id回填到插入的对象中
          val generator = resolveKeyGenerator(modelClass)
          val keyGenerator = if (generator != null) {
            IdGeneratorUtil.getGenerator(generator)
          } else if (tableInfo.idType == IdType.AUTO) {
            Jdbc3KeyGenerator.INSTANCE
          } else {
            NoKeyGenerator.INSTANCE
          }
          addInsertMappedStatement(
              mapperClass, modelClass, methodInfo.name, sqlSource,
              keyGenerator, tableInfo.keyProperty, tableInfo.keyColumn
          )
        }

        MethodType.Update,
        MethodType.LogicDelete -> {
          addUpdateMappedStatement(mapperClass, modelClass, methodInfo.name, sqlSource)
        }

      }
    } catch (ex: Exception) {
      LOG.error(
          """
  方法${resolvedQuery.methodInfo.fullMethodName}解析出错 >>>>>>>>
    成功解析SQL但注入mybatis失败，失败的SQL为：
  $sql
  
    可能存在下列情形之一：
  ${possibleErrors.joinToString { "\n\t\t-\t%s\n".format(it) }}""".trimIndent(), ex
      )
    }
  }

  private fun resolveKeyGenerator(modelClass: Class<*>): String? {
    val fields = MappingResolver.resolveFields(modelClass)
    val generatedIdField = fields.firstOrNull {
      AnnotationSupport.isIdField(it) && AnnotationSupport.hasGeneratedValueAnnotation(it)
    }
    if (generatedIdField != null) {
      val generator = getGenerator(generatedIdField)
      if (generator.isNotNullAndNotBlank()) {
        return generator
      }
    }
    return null
  }

  private fun getGenerator(field: Field): String? {
    AnnotationSupport.GENERATED_VALUE_ANNOTATIONS.forEach {
      val anno = AnnotationUtil.getAnnotation(field, it)
      if (anno != null) {
        return AnnotationUtil.getAnnotationFieldValue<Any>(anno, "generator").toString()
      }
    }
    return null
  }

  private fun resolveCount(
      resolvedQuery: ResolvedQuery,
      methodInfo: MethodInfo,
      modelClass: Class<*>,
      mapperClass: Class<*>,
      sqlSource: SqlSource?,
      tableInfo: TableInfo
  ) {
    val returnType = methodInfo.returnType
    val resultMap = if (resolvedQuery.query != null) {
      QueryResolver.resolveResultMap(
          methodInfo, resolvedQuery.query!!
      )
    } else {
      null
    }
    // addSelectMappedStatement这个方法中会使用默认的resultMap，该resultMap映射的类型和modelClass一致，所以如果当前方法的返回值和modelClass
    // 不一致时，不能使用该方法，否则会产生类型转换错误
    if (returnType == modelClass && resultMap == null) {
      addSelectMappedStatementForTable(mapperClass, methodInfo.name, sqlSource, tableInfo)
    } else {
      addMappedStatement(
          mapperClass, methodInfo.name,
          sqlSource, SqlCommandType.SELECT, null, resultMap, returnType,
          NoKeyGenerator(), null, null
      )
    }
    // 为select查询自动生成count的statement，用于分页时查询总数
    if (resolvedQuery.type == MethodType.Select) {
      addSelectMappedStatementForOther(
          mapperClass, methodInfo.name + COUNT_STATEMENT_SUFFIX,
          languageDriver.createSqlSource(
              configuration,
              "<script>${resolvedQuery.query!!.buildCountSql()}</script>", modelClass
          ),
          Long::class.java
      )
    }
  }

}
